package cn.moi.util.watermark.utils;

import cn.hutool.core.io.FileUtil;
import cn.moi.util.watermark.config.IconWatermarkConfig;
import cn.moi.util.watermark.config.TextWatermarkConfig;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.util.List;

/**
 * 类说明：WatermarkUtils
 *
 * @author Moi-Himmel
 * @since 2023/10/28
 **/
@UtilityClass
@SuppressWarnings("unused")
public class WatermarkUtils {

    private static final String IMAGE_JPG = "jpg";

    /**
     * 为图片添加文字水印
     *
     * @param srcImgFile 源文件
     * @param outImgFile 输出文件
     * @param textConfig 文字水印配置
     */
    @SneakyThrows
    public void markImgWithText(File srcImgFile, File outImgFile, TextWatermarkConfig textConfig) {
        if (!CommonUtils.checkFileExist(outImgFile)) {
            System.out.println("文件创建失败");
            return;
        }
        try (FileOutputStream outputStream = new FileOutputStream(outImgFile)) {
            // 读取图片文件信息
            BufferedImage bufferedImage = CommonUtils.copyImageFileToNewImage(srcImgFile);
            // 添加文字水印
            CommonUtils.addTextToImage(bufferedImage, textConfig);
            // 输出图片
            ImageIO.write(bufferedImage, IMAGE_JPG, outputStream);
        }
    }

    /**
     * 为图片添加图标水印
     *
     * @param iconFile        图标文件
     * @param srcImgFile      源文件
     * @param outImgFile      输出文件
     * @param watermarkConfig 水印配置
     */
    @SneakyThrows
    public void markImgWithIcon(File iconFile, File srcImgFile, File outImgFile, IconWatermarkConfig watermarkConfig) {
        if (!CommonUtils.checkFileExist(outImgFile)) {
            System.out.println("文件创建失败");
            return;
        }
        try (OutputStream outputStream = Files.newOutputStream(outImgFile.toPath())) {
            BufferedImage bufferedImage = CommonUtils.copyImageFileToNewImage(srcImgFile);
            BufferedImage iconImage = ImageIO.read(iconFile);
            CommonUtils.addIconToImage(iconImage, bufferedImage, watermarkConfig);
            // 生成图片
            ImageIO.write(bufferedImage, IMAGE_JPG, outputStream);
        }
    }

    /**
     * 为PDF添加文字水印
     *
     * @param srcFile    源文件
     * @param outFile    输出文件
     * @param textConfig 水印配置
     */
    public void markPdfWithText(File srcFile, File outFile, TextWatermarkConfig textConfig) {
        try (PDDocument srcDoc = Loader.loadPDF(srcFile)) {
            // 添加密码，防止文档被编辑
            CommonUtils.encryptPDFDocument(srcDoc, "1234", null);
            // 引入字体文件，并计算水印长宽
            PDFont font = PDType0Font.load(srcDoc, FileUtil.getInputStream(CommonUtils.getFontPath(textConfig)),
                    true);
            float textWidth = font.getStringWidth(textConfig.getText()) / 1000 * textConfig.getFontSize();
            float textHeight = font.getBoundingBox().getHeight() / 1000 * textConfig.getFontSize();
            // 水印透明度和颜色配置
            PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
            graphicsState.setNonStrokingAlphaConstant(textConfig.getAlpha() / 255f);
            graphicsState.setAlphaSourceFlag(true);
            List<Integer> color = textConfig.getColor();
            Color fontColor = new Color(color.get(0), color.get(1), color.get(2));
            boolean isSingleWatermark = textConfig.getLatSpace() == 0 && textConfig.getLonSpace() == 0;
            double rotation = Math.toRadians(textConfig.getRotation());
            // 遍历PDF文档所有页面
            for (PDPage page : srcDoc.getPages()) {
                try (PDPageContentStream contentStream = new PDPageContentStream(srcDoc, page,
                        PDPageContentStream.AppendMode.APPEND, true, true)) {
                    contentStream.setGraphicsStateParameters(graphicsState);
                    // 水印颜色
                    contentStream.setNonStrokingColor(fontColor);
                    // 水印字体
                    contentStream.setFont(font, textConfig.getFontSize());
                    // 开始写入水印操作
                    contentStream.beginText();
                    // 获取页面尺寸
                    PDRectangle mediaBox = page.getMediaBox();
                    float boxWidth = mediaBox.getWidth();
                    float boxHeight = mediaBox.getHeight();
                    Point2D.Float rotateCenter = new Point2D.Float();
                    rotateCenter.setLocation(boxWidth / 2f, boxHeight / 2f);
                    // 考虑有些页面被旋转过，需要将页面旋转的角度抵消掉
                    int pageRotation = page.getRotation();
                    double finalRotation = rotation + Math.toRadians(pageRotation);
                    if (isSingleWatermark) {
                        Matrix matrix = CommonUtils.calculateMatrix(textWidth, textHeight, finalRotation, rotateCenter);
                        contentStream.setTextMatrix(matrix);
                        contentStream.showText(textConfig.getText());
                    } else {
                        List<Point2D.Float> matrix = CommonUtils.calculateMatrix(boxWidth, boxHeight, textWidth,
                                textHeight, textConfig.getLatSpace(), textConfig.getLonSpace(), rotateCenter, finalRotation);
                        Matrix rotateInstance;
                        for (Point2D.Float point : matrix) {
                            rotateInstance = Matrix.getRotateInstance(finalRotation, point.x, point.y);
                            contentStream.setTextMatrix(rotateInstance);
                            contentStream.showText(textConfig.getText());
                        }
                    }
                    contentStream.endText();
                    contentStream.restoreGraphicsState();
                }
            }
            srcDoc.save(outFile);
        } catch (IOException e) {
            System.out.println("PDF文件读取异常：" + srcFile.getName());
            throw new RuntimeException(e);
        }
    }

    /**
     * 为PDF添加图标水印
     *
     * @param iconFile  图标文件
     * @param srcFile   源文件
     * @param outFile   输出文件
     * @param imgConfig 水印配置
     */
    public void markPdfWithIcon(File iconFile, File srcFile, File outFile, IconWatermarkConfig imgConfig) {
        try (PDDocument srcDoc = Loader.loadPDF(srcFile)) {
            // 添加密码，防止文档被编辑
            CommonUtils.encryptPDFDocument(srcDoc, "1234", null);
            // 将水印图片引入文档
            PDImageXObject icon = PDImageXObject.createFromFileByExtension(iconFile, srcDoc);
            float iconWidth = (float) (icon.getWidth() * imgConfig.getZoom());
            float iconHeight = (float) (icon.getHeight() * imgConfig.getZoom());

            // 水印透明度和颜色配置
            PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
            graphicsState.setNonStrokingAlphaConstant(imgConfig.getAlpha() / 255f);
            graphicsState.setAlphaSourceFlag(true);
            boolean isSingleWatermark = imgConfig.getLatSpace() == 0 && imgConfig.getLonSpace() == 0;
            double rotation = Math.toRadians(imgConfig.getRotation() == 0 ? 1 : imgConfig.getRotation());
            // 遍历PDF文档所有页面
            for (PDPage page : srcDoc.getPages()) {
                try (PDPageContentStream contentStream = new PDPageContentStream(srcDoc, page,
                        PDPageContentStream.AppendMode.APPEND, true, true)) {
                    contentStream.setGraphicsStateParameters(graphicsState);
                    // 获取页面尺寸
                    PDRectangle mediaBox = page.getMediaBox();
                    float boxWidth = mediaBox.getWidth();
                    float boxHeight = mediaBox.getHeight();
                    Point2D.Float rotateCenter = new Point2D.Float();
                    rotateCenter.setLocation(boxWidth / 2f, boxHeight / 2f);
                    // 考虑有些页面被旋转过，需要将页面旋转的角度抵消掉
                    double finalRotation = rotation + Math.toRadians(page.getRotation());
                    if (isSingleWatermark) {
                        Matrix matrix = CommonUtils.calculateMatrix(iconWidth, iconHeight, finalRotation, rotateCenter);
                        matrix.scale(iconWidth, iconHeight);
                        contentStream.drawImage(icon, matrix);
                    } else {
                        List<Point2D.Float> matrix = CommonUtils.calculateMatrix(boxWidth, boxHeight, iconWidth,
                                iconHeight, imgConfig.getLatSpace(), imgConfig.getLonSpace(), rotateCenter, finalRotation);
                        Matrix rotateInstance;
                        for (Point2D.Float point : matrix) {
                            rotateInstance = Matrix.getRotateInstance(finalRotation, point.x, point.y);
                            rotateInstance.scale(iconWidth, iconHeight);
                            contentStream.drawImage(icon, rotateInstance);
                        }
                    }
                    contentStream.restoreGraphicsState();
                }
            }
            srcDoc.save(outFile);
        } catch (IOException e) {
            System.out.println("PDF文件读取异常：" + srcFile.getName());
            throw new RuntimeException(e);
        }
    }

    /**
     * 为Word文档添加文字水印，如果横纵间隔为0，在页面中心创建单个水印，否则尝试将水印铺满整个页面
     *
     * @param srcFile    源文件
     * @param outFile    输出文件
     * @param textConfig 水印配置
     */
    @SneakyThrows
    public void markDocxWithText(File srcFile, File outFile, TextWatermarkConfig textConfig) {
        if (!CommonUtils.checkFileExist(outFile)) {
            System.out.println("文件创建失败");
            return;
        }
        try (BufferedInputStream buffIn = FileUtil.getInputStream(srcFile);
             BufferedOutputStream buffOut = FileUtil.getOutputStream(outFile);
             XWPFDocument doc = new XWPFDocument(buffIn)) {
            if (textConfig.getLatSpace() == 0 && textConfig.getLonSpace() == 0) {
                CommonUtils.createSingleWordWatermark(doc, textConfig);
            } else {
                // 平铺时为保证兼容性，采取转为水印图片的方式实现
                CommonUtils.createMultiWordWatermarkByImage(doc, textConfig);
            }
            doc.write(buffOut);
        }
    }

    /**
     * 为Word文档添加图片水印
     *
     * @param iconFile  水印图片
     * @param srcFile   源文件
     * @param outFile   输出文件
     * @param imgConfig 图片水印配置
     */
    @SneakyThrows
    public void markDocxWithImage(File iconFile, File srcFile, File outFile, IconWatermarkConfig imgConfig) {
        if (!CommonUtils.checkFileExist(outFile)) {
            System.out.println("文件创建失败");
            return;
        }
        try (BufferedInputStream buffIn = FileUtil.getInputStream(srcFile);
             BufferedOutputStream buffOut = FileUtil.getOutputStream(outFile);
             XWPFDocument doc = new XWPFDocument(buffIn)) {
            if (imgConfig.getLatSpace() == 0 && imgConfig.getLonSpace() == 0) {
                CommonUtils.createSingleImageWatermark(iconFile, doc, imgConfig);
            } else {
                CommonUtils.createMultiImageWatermark(iconFile, doc, imgConfig);
            }
            doc.write(buffOut);
        }
    }

    /**
     * 为Excel添加文字水印
     *
     * @param srcFile    源文件
     * @param outFile    输出文件
     * @param textConfig 水印配置
     */
    @SneakyThrows
    public void markExcelWithText(File srcFile, File outFile, TextWatermarkConfig textConfig) {
        if (!CommonUtils.checkFileExist(outFile)) {
            System.out.println("文件创建失败");
            return;
        }
        // 计算水印图片的长宽并创建 BufferedImage 对象
        Font font = new Font(textConfig.getFontName(), Font.PLAIN, textConfig.getFontSize());
        Dimension textDimension = CommonUtils.getTextDimension(textConfig.getText(), font);
        Dimension markImgDimension = CommonUtils.getMarkDimension(textDimension, textConfig.getRotation(),
                textConfig.getLatSpace(), textConfig.getLonSpace());
        BufferedImage markImage = CommonUtils.createBackGroundImage(markImgDimension);

        // 将水印文字加入背景图片
        Graphics2D graphics2D = markImage.createGraphics();
        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics2D.setFont(font);
        graphics2D.setColor(new Color(textConfig.getColor().get(0), textConfig.getColor().get(1),
                textConfig.getColor().get(2), textConfig.getAlpha()));
        double x = (double) markImage.getWidth() / 2;
        double y = (double) markImage.getHeight() / 2;
        graphics2D.rotate(-Math.toRadians(textConfig.getRotation()), x, y);
        graphics2D.drawString(textConfig.getText(), (int) x, (int) y);
        graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        graphics2D.dispose();

        CommonUtils.writeMarkIntoExcel(srcFile, outFile, markImage);
    }

    /**
     * 为Excel添加图片水印
     *
     * @param iconFile    水印图标文件
     * @param srcFile     源文件
     * @param outFile     输出文件
     * @param imageConfig 水印配置
     */
    @SneakyThrows
    public void markExcelWithImage(File iconFile, File srcFile, File outFile, IconWatermarkConfig imageConfig) {
        if (!CommonUtils.checkFileExist(outFile)) {
            System.out.println("文件创建失败");
            return;
        }
        // 计算水印图片的长宽并创建 BufferedImage 对象
        BufferedImage iconImage = ImageIO.read(iconFile);
        Double zoom = imageConfig.getZoom();
        int scaledWidth = (int) (zoom * iconImage.getWidth(null));
        int scaledHeight = (int) (zoom * iconImage.getHeight(null));
        Dimension iconDimension = new Dimension(scaledWidth, scaledHeight);
        Dimension markImgDimension = CommonUtils.getMarkDimension(iconDimension, imageConfig.getRotation(),
                imageConfig.getLatSpace(), imageConfig.getLonSpace());
        BufferedImage markImage = CommonUtils.createBackGroundImage(markImgDimension);

        // 将图标写入水印背景
        Graphics2D graphics2D = markImage.createGraphics();
        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        double x = (double) markImage.getWidth() / 2;
        double y = (double) markImage.getHeight() / 2;
        graphics2D.rotate(-Math.toRadians(imageConfig.getRotation()), x, y);
        Image scaledIcon = iconImage.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_DEFAULT);
        float alpha = imageConfig.getAlpha() / 255f;
        graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
        x = x - scaledWidth / 2d;
        y = y - scaledHeight / 2d;
        graphics2D.drawImage(scaledIcon, (int) x, (int) y, null);
        graphics2D.dispose();

        CommonUtils.writeMarkIntoExcel(srcFile, outFile, markImage);
    }

}
